# 给你一个整数数组 nums ，找到其中最长严格递增子序列的长度。
#  子序列 是由数组派生而来的序列，删除（或不删除）数组中的元素而不改变其余元素的顺序。例如，[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
#
#  示例 1：
# 输入：nums = [10,9,2,5,3,7,101,18]
# 输出：4
# 解释：最长递增子序列是 [2,3,7,101]，因此长度为 4 。
#
#  示例 2：
# 输入：nums = [0,1,0,3,2,3]
# 输出：4
#
#  示例 3：
# 输入：nums = [7,7,7,7,7,7,7]
# 输出：1
#  进阶：
#  你能将算法的时间复杂度降低到 O(n log(n)) 吗?
from typing import List


class Solution:
    def lengthOfLIS2(self, nums: List[int]) -> int:
        """
        方法二:贪心 + 二分查找
        思路：
        要使上升子序列尽可能的长，则需要让序列上升得尽可能慢，因此希望每次在上升子序列最后加上的那个数尽可能的小

        实现:
        维护一个 d (list)，并用 res 记录目前最长上升子序列的长度，
        d[i]，表示长度为 i 的最长上升子序列的末尾元素的最小值(因为长度为i的最长递增子序列可能不止一个)，那么 d[i] 是关于 i 单调递增的
        起始时 res 为 1，d = [nums[0]]。
        依次遍历数组nums[1:] 中的每个元素，并按如下规则更新数组 d 和 res 的值。
            如果 nums[i] > d[res]
                则更新 res += 1，
            否则
                在 d[1 ... res]中找到第一个比 nums[i] 大的数 记录其位置为 loc(因为d[i] 是关于 i 单调递增的，所以可以用二分法进行优化)，
                更新 d[loc] = nums[i]。
        :param nums:
        :return:
        """
        res, d = 1, [nums[0]]
        for num in nums[1:]:
            if num > d[-1]:
                res += 1
                d.append(num)
            else:
                # 遍历寻找第一个比 nums[i] 大的数
                # loc = 0
                # for i in range(res):
                #     if d[i] >= num:
                #         loc = i
                #         break
                # d[loc] = num
                # 二分查找第一个比 nums[i] 大的数
                left, right, loc = 0, res - 1, 0
                while left <= right:
                    mid = (left + right) // 2
                    if d[mid] >= num:
                        loc = mid
                        right = mid - 1
                    else:
                        left = mid + 1
                d[loc] = num
        return res

    def lengthOfLIS1(self, nums: List[int]) -> int:
        """
        解法一：动态规划
        :param nums:
        :return:
        """
        size, res = len(nums), 1
        dp = [1] * size  # dp[i] = m 表示考虑前 i个元素, 以 nums[i]为结尾的最长严格递增子序列的长度为 m
        for i in range(1, size):
            # 在当前元素之前的所有元素中找到满足 nums[j] < nums[i] 的 max(dp[j])的值，即dp(i) = max(dp(j)) + 1(nums[j] < nums[i])
            for j in range(i):
                if nums[j] > nums[i]:
                    dp[i] = max(dp[i], dp[j] + 1)
            res = max(dp[i], res)
        return res

    def lengthOfLIS(self, nums: List[int]) -> int:
        return self.lengthOfLIS2(nums)


if __name__ == "__main__":
    nums = [10, 9, 2, 5, 3, 7, 101, 18]
    # nums = [0, 1, 0, 3, 2, 3]
    # nums = [7, 7, 7, 7, 7, 7, 7]
    print(Solution().lengthOfLIS(nums))
